#pragma once

#include <chrono>
#include <sstream>
#include <windows.h>
#include <collection.h>
#include <wrl.h>
//#include <functional>

using namespace Platform;
using namespace Windows::Storage;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;

namespace mbase {

ref class Number sealed {
private:
	int n;
public:
	property int N { int get() { return n; } }
	Number(int _n) : n(_n) {}
	String^ ToString() { std::wstringstream ss; ss << n; return ref new String(ss.str().c_str()); }
};

ref class RealNumber sealed {
private:
	float n;
public:
	property float N { float get() { return n; } }
	RealNumber(float _n) : n(_n) {}
	String^ ToString() { std::wstringstream ss; ss << n; return ref new String(ss.str().c_str()); }
};

ref class UniqueID sealed {
public:
	property String^ ID;
	UniqueID(String^ id) { ID = id; }
};

delegate void StringPropertyCallback(String^ key, String^ value);
delegate void NumberPropertyCallback(String^ key, int value);
delegate void FloatPropertyCallback(String^ key, float value);
delegate void ObjectPropertyCallback(String^ key, Object^ value);
delegate void PropertyChangedCallback(String^ key);

ref class MetroClass abstract {
private:
	String^ uid;
protected:
	Map<String^,String^>^ strProps;
	Map<String^,Number^>^ numProps;
	Map<String^,RealNumber^>^ fltProps;
	Map<String^,MetroClass^>^ objProps;
public:
	event PropertyChangedCallback^ WillChangeProperty;
	event PropertyChangedCallback^ DidChangeProperty;
	virtual void DidInit() {}
	virtual void WillStore() {}
	~MetroClass() { OutputDebugStringW((L"Dealloc (" + GetClassName() + ")\n")->Data()); }
	bool operator==(MetroClass^ obj) { return obj != nullptr && GetUID() == obj->GetUID(); }
	String^ GetUID() { return uid; }
	//void SetUID(String^ id) { uid = id; }
	IMap<String^,String^>^ GetStringProperties() { return strProps; }
	IMap<String^,Number^>^ GetNumberProperties() { return numProps; }
	IMap<String^,RealNumber^>^ GetFloatProperties() { return fltProps; }
	IMap<String^,MetroClass^>^ GetObjectProperties() { return objProps; }
	void EnumerateStringProperties(StringPropertyCallback^ callback) {
		if (strProps->Size > 0) {
			auto iter = strProps->First();
			do {
				auto key = iter->Current->Key;
				//callback(key, strProps->Lookup(key));
				callback(key, iter->Current->Value);
			} while (iter->MoveNext());
		}
	}
	void EnumerateNumberProperties(NumberPropertyCallback^ callback) {
		if (numProps->Size > 0) {
			auto iter = numProps->First();
			do {
				auto key = iter->Current->Key;
				//callback(key, numProps->Lookup(key)->N);
				callback(key, iter->Current->Value->N);
			} while (iter->MoveNext());
		}
	}
	void EnumerateFloatProperties(FloatPropertyCallback^ callback) {
		if (fltProps->Size > 0) {
			auto iter = fltProps->First();
			do {
				auto key = iter->Current->Key;
				//callback(key, numProps->Lookup(key)->N);
				callback(key, iter->Current->Value->N);
			} while (iter->MoveNext());
		}
	}
	void EnumerateObjectProperties(ObjectPropertyCallback^ callback) {
		if (objProps->Size > 0) {
			auto iter = objProps->First();
			do {
				auto key = iter->Current->Key;
				//callback(key, objProps->Lookup(key));
				callback(key, iter->Current->Value);
			} while (iter->MoveNext());
		}
	}
	String^ GetClassName() {
		IInspectable* insp = (IInspectable*) this;
		HSTRING str;
		insp->GetRuntimeClassName(&str);
		UINT32 len;
		std::wstring s = WindowsGetStringRawBuffer(str, &len);
		auto name = ref new String(s.c_str());
		WindowsDeleteString(str);
		return name;
	}
	MetroClass() {
		strProps = ref new Map<String^,String^>;
		numProps = ref new Map<String^,Number^>;
		fltProps = ref new Map<String^,RealNumber^>;
		objProps = ref new Map<String^,MetroClass^>;
		std::wstringstream ss;
		auto now = std::chrono::system_clock::now();
		ss << std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
		ss << ":" << rand();
		uid = ref new String(ss.str().c_str());
	}
	MetroClass(UniqueID^ _uid) {
		strProps = ref new Map<String^,String^>;
		numProps = ref new Map<String^,Number^>;
		fltProps = ref new Map<String^,RealNumber^>;
		objProps = ref new Map<String^,MetroClass^>;
		uid = _uid->ID;
	}
	Number^ GetNumProperty(String^ name) { return numProps->Lookup(name); }
	RealNumber^ GetFltProperty(String^ name) { return fltProps->Lookup(name); }
	String^ GetStrProperty(String^ name) { return strProps->Lookup(name); }
	MetroClass^ GetObjProperty(String^ name) { return objProps->Lookup(name); }
	void RemoveObjectProperty(String^ name) { objProps->Remove(name); }
	bool HasObjectProperty(String^ name) { return objProps->HasKey(name); }
	virtual String^ Description() { return "???"; }
	String^ ToString() {
		String^ retVal = GetClassName() + ":";
		if (GetClassName() == "PVector" || GetClassName() == "PMap") {
			retVal += " " + this->Description();
			return retVal;
		}
		if (strProps->Size > 0) {
		auto iter = strProps->First();
		do {
			auto key = iter->Current->Key;
			retVal += " " + key + "=" + strProps->Lookup(key);
		} while (iter->MoveNext());
		}
		if (numProps->Size > 0) {
			auto iter2 = numProps->First();
			do {
				auto key = iter2->Current->Key;
				retVal += " " + key + "=" + numProps->Lookup(key)->ToString();
			} while (iter2->MoveNext());
		}
		if (fltProps->Size > 0) {
			auto iter2b = fltProps->First();
			do {
				auto key = iter2b->Current->Key;
				retVal += " " + key + "=" + fltProps->Lookup(key)->ToString();
			} while (iter2b->MoveNext());
		}
		if (objProps->Size > 0) {
		auto iter3 = objProps->First();
		do {
			auto key = iter3->Current->Key;
			retVal += " " + key + "=(" + objProps->Lookup(key)->ToString() + ")";
		} while (iter3->MoveNext());
		}
		return retVal;
	}
};

}
